#region Imported Namespaces


using System;
using System.Collections;
using System.Collections.Specialized;


#endregion


namespace WtfManager.HierarchicalData
{
  ///<summary>
  /// A base class for hierarchy items.
  ///</summary>
  public abstract class TreeItem
  {
    #region Fields


    private readonly TreeItemsCollection _children = new TreeItemsCollection();
    private readonly ReadOnlyTreeItemsCollection _readOnlyChildrenCollection;
    private bool _isLoaded;
    private TreeItem _parent;


    #endregion


    #region Constructors/Finalizers


    ///<summary>
    /// Default TreeItem constructor.
    ///</summary>
    protected TreeItem()
    {
      _readOnlyChildrenCollection = new ReadOnlyTreeItemsCollection(_children);
      _children.CollectionChanged += ChildrenCollectionChanged;
    }


    #endregion


    #region Properties/Indexers


    ///<summary>
    /// Gets a collection of child <see cref="TreeItem">tree items</see>.
    ///</summary>
    public ReadOnlyTreeItemsCollection Children
    {
      get
      {
        if (!IsLoaded)
        {
          Load();
        }

        return _readOnlyChildrenCollection;
      }
    }


    ///<summary>
    /// Returns true if an item was already loaded, otherwise false.
    ///</summary>
    public bool IsLoaded
    {
      get
      {
        return _isLoaded;
      }
    }


    public abstract bool IsMovable
    {
      get;
    }


    ///<summary>
    /// Gets a name of tree item.
    ///</summary>
    public string Name
    {
      get;
      set;
    }


    ///<summary>
    /// Gets a parent tree item.
    ///</summary>
    public TreeItem Parent
    {
      get
      {
        return _parent;
      }
      private set
      {
        _parent = value;

        OnParentChanged();
      }
    }


    ///<summary>
    /// Gets Tree Item path in hierarchy.
    ///</summary>
    public string Path
    {
      get
      {
        return GetPath();
      }
    }


    #endregion


    #region Methods


    private void Load()
    {
      _isLoaded = true;

      Refresh();
    }


    /// <summary>
    /// Adds a child to <see cref="Children">collection</see>.
    /// </summary>
    /// <exception cref="InvalidOperationException">An item was already added to collection.</exception>
    protected void AddChild(TreeItem child)
    {
      if (_children.Contains(child))
      {
        throw new InvalidOperationException("An item was already added to collection.");
      }

      _children.Add(child);
    }


    ///<summary>
    /// Removes all children from <see cref="Children">collection</see>.
    ///</summary>
    protected void RemoveChildren()
    {
      _children.Clear();
    }


    ///<summary>
    /// Removes a child from <see cref="Children">collection</see>.
    ///</summary>
    ///<param name="child">A <see cref="TreeItem"/> to remove.</param>
    ///<returns>true if item is successfully removed; otherwise, false. This method also returns false if item was not found in
    ///the original collection.</returns>
    protected bool RemoveChild(TreeItem child)
    {
      return _children.Remove(child);
    }


    ///<summary>
    /// Called when parent tree item was changed.
    ///</summary>
    protected virtual void OnParentChanged()
    {
    }


    private void ChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      switch (e.Action)
      {
        case NotifyCollectionChangedAction.Add:
          ProcessNewItems(e.NewItems);
          break;
        case NotifyCollectionChangedAction.Remove:
          ProcessOldItems(e.OldItems);
          break;

        case NotifyCollectionChangedAction.Reset:
        case NotifyCollectionChangedAction.Replace:
          if (e.OldItems != null)
          {
            ProcessOldItems(e.OldItems);
          }

          if (e.NewItems != null)
          {
            ProcessNewItems(e.NewItems);
          }
          break;
      }
    }


    private static void ProcessOldItems(IList items)
    {
      foreach (TreeItem fileSystemItem in items)
      {
        fileSystemItem.Parent = null;
      }
    }


    private void ProcessNewItems(IList items)
    {
      foreach (TreeItem fileSystemItem in items)
      {
        fileSystemItem.Parent = this;
      }
    }


    public void Refresh()
    {
      OnRefresh();

      _isLoaded = true;
    }


    protected abstract void OnRefresh();


    ///<summary>
    /// Gets Tree Item path in hierarchy.
    ///</summary>
    ///<returns></returns>
    public string GetPath()
    {
      return Parent == null ? Name : string.Format("{0}\\{1}", Parent.GetPath(), Name);
    }


    ///<summary>
    /// gets a tree item by a specified relative path in hierarchy.
    ///</summary>
    ///<param name="relativePath"></param>
    ///<returns></returns>
    public TreeItem GetItem(string relativePath)
    {
      string[] split = relativePath.Split(new[] {"\\"}, StringSplitOptions.RemoveEmptyEntries);

      TreeItem item = this;

      for (int i = 0; i < split.Length; i++)
      {
        if (!item.IsLoaded)
        {
          item.Load();
        }

        item = item.Children[split[i]];

        if (item == null)
        {
          break;
        }
      }

      return item;
    }


    public bool Contains(TreeItem item)
    {
      foreach (TreeItem child in _children)
      {
        if (child == item)
        {
          return true;
        }

        if (child.Contains(item))
        {
          return true;
        }
      }

      return false;
    }


    protected virtual void ValidateMoveTo(TreeItem target)
    {
      if (!IsMovable)
      {
        throw new InvalidOperationException("'" + this + "' cannot be moved.");
      }

      if (target == this)
      {
        throw new InvalidOperationException("Cannot move '" + this + "' into itself.");
      }

      if (target == Parent)
      {
        throw new InvalidOperationException("Current '" + this + "' parent is the same as specified move target.");
      }

      if (Contains(target))
      {
        throw new InvalidOperationException("Cannot move '" + this + "' into it's child.");
      }
    }


    public bool CanMoveTo(TreeItem target)
    {
      try
      {
        ValidateMoveTo(target);
      }
      catch
      {
        return false;
      }

      return true;
    }


    #endregion
  }
}